/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package database;

import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import dto.Event;
import dto.ImageEvent;
import dto.Metadata;
import dto.TextEvent;
import dto.Track;
import dto.VideoEvent;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultListModel;
import resources.ResourceManager;

/**
 * Database Singleton, handles the Connection and does operations on the database
 * 
 * @author steffen
 */
public class DatabaseManager {
    
    private static final String DATABASE_DRIVER = "jdbc:derby:";
    private static final String DATABASE_EXTENSION = ";create=true";
    
    public static final String DATABASE_PREF_KEY    = "";
    
    //This is the ready driver string, only for test purpose
    private static final String  DATABASE_URL = "jdbc:derby:../Database;create=true";
    
    private ConnectionSource connectionSource;
    
    private static DatabaseManager instance;
    private Dao<Track, Integer> trackDao;
    private Dao<Metadata, Track> metadataDao;
    //private Dao<Image, Integer> imageDao;
    private Dao<ImageEvent, Track> imageEventDao;
    private Dao<TextEvent, Track> textEventDao;
    private Dao<VideoEvent, Track> videoEventDao;
    
    /**
     * Get the instance of the DatabaseManager
     * 
     * @return the instance of the DatabaseManager
     */
    public static DatabaseManager getInstance() {
        if(instance == null)
            instance = new DatabaseManager();
        
        return instance;
    }
    
    public DatabaseManager() {
        try {
            Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
            connectionSource = new JdbcConnectionSource(getDatabasePath());
            
            trackDao = DaoManager.createDao(connectionSource, Track.class);
            metadataDao = DaoManager.createDao(connectionSource, Metadata.class);
            //imageDao = DaoManager.createDao(connectionSource, Image.class);
            imageEventDao = DaoManager.createDao(connectionSource, ImageEvent.class);
            videoEventDao = DaoManager.createDao(connectionSource, VideoEvent.class);
            textEventDao = DaoManager.createDao(connectionSource, TextEvent.class);
            
            try {
                initDatabase();
            } catch(SQLException e) {
                //System.out.println(e.getMessage());
            }
            
        } catch(Exception e) {
            //e.printStackTrace();
        }
    }
    
    private void initDatabase() throws SQLException {
        TableUtils.createTableIfNotExists(connectionSource, Track.class);
        //TableUtils.createTableIfNotExists(connectionSource, Image.class);
        TableUtils.createTableIfNotExists(connectionSource, Metadata.class);
        TableUtils.createTableIfNotExists(connectionSource, ImageEvent.class);
        TableUtils.createTableIfNotExists(connectionSource, VideoEvent.class);
        TableUtils.createTableIfNotExists(connectionSource, TextEvent.class);
    }
    
    private String getDatabasePath() {
        ResourceManager resMan = new ResourceManager();
        
        String path = resMan.getProperty(ResourceManager.DATABASE_PATH);
        
        return DATABASE_DRIVER + path + "/my_tracks.db" + DATABASE_EXTENSION;
    }
    
    protected void saveImageEvent(ImageEvent event) throws SQLException {
        if(imageEventDao != null) {
            imageEventDao.createOrUpdate(event);
        }
    }
    
    protected void saveTextEvent(TextEvent event) throws SQLException {
        if(textEventDao != null) {
            textEventDao.createOrUpdate(event);
        }
    }
    
    protected void saveVideoEvent(VideoEvent event) throws SQLException {
        if(videoEventDao != null)
            videoEventDao.createOrUpdate(event);
    }
    
    /**
     * Update the given ImageEvent with the given parent
     * 
     * @param event The event which should be updated.
     * @param parent The parent of the event.
     */
    public void updateImageEvent(ImageEvent event, Track parent) 
            throws SQLException {
        event.setParentTrack(parent);
        saveImageEvent(event);
    }
    
    /**
     * Update the given TextEvent with the given Parent.
     * 
     * @param event The event which should be updated.
     * @param parent The parent of the event.
     */
    public void updateTextEvent(TextEvent event, Track parent) 
            throws SQLException {
        event.setParentTrack(parent);
        saveTextEvent(event);
    }
    
    /**
     * Update the VideoEvent with the given Parent.
     * 
     * @param event The event which should be updated.
     * @param parent The Parent of the Evenet.
     */
    public void updateVideoEvent(VideoEvent event, Track parent) 
            throws SQLException {
        event.setParentTrack(parent);
        saveVideoEvent(event);
    }
    
    /**
     * Update an Event in the database. This Event must be already in the 
     * database.
     * 
     * The parent must also be in the database already.
     * 
     * @param event The Event with the new Data.
     * @param parent The parent of the Event.
     * 
     * @throws SQLException 
     */
    public void updateEvent(Event event, Track parent) 
            throws SQLException {
        if(trackDao.queryForSameId(parent) != null) {
            event.setParentTrack(parent);
            
            if(event instanceof ImageEvent) {
                updateImageEvent((ImageEvent) event, parent);
            } else if(event instanceof VideoEvent) {
                updateVideoEvent((VideoEvent) event, parent);
            } else if(event instanceof TextEvent) {
                updateTextEvent((TextEvent) event, parent);
            } else {
                //System.out.println("Dafuq Event");
            }
        }
    }
    
    /**
     * Saves an Event subclass with the given Parent 
     * to the database.
     * 
     * The parent cant be null and must already in the Database.
     * 
     * @param event The event which should be stored in the
     *      database.
     * @param parent The Parent of the event.
     * @throws SQLException 
     */
    public void saveEvent(Event event, Track parent) 
            throws SQLException {
        if(trackDao.queryForSameId(parent) != null) {
            event.setParentTrack(parent);
            
            if(event instanceof ImageEvent) {
                saveImageEvent((ImageEvent) event);
            } else if(event instanceof VideoEvent) {
                saveVideoEvent((VideoEvent) event);
            } else if(event instanceof TextEvent) {
                saveTextEvent((TextEvent) event);
            } else {
                //System.out.println("Dafuq Event");
            }
        }
    }
    
    /**
     * Save a given Track.
     * 
     * This will traverse through all Events of the internal List
     * and save them to the Database.
     * This method should not be called if the Events are already in the 
     * Database.
     * 
     * @param track The Track which should be saved.
     * @throws SQLException Throws an SQLException if there was any 
     *      communication error with the database.
     */
    public void saveTrack(Track track) throws SQLException {        
        trackDao.create(track);
        
        if(track.getMetadata() != null) {
            Metadata met = track.getMetadata();
            met.setParentTrack(track);
        
            metadataDao.createOrUpdate(met);
        }
        
        if(track.getEvents() != null) {
            DefaultListModel<Event> events = track.getEvents();
            for(int i = 0; i < events.size(); i++) {
                events.get(i).setParentTrack(track);
                if(events.get(i) instanceof ImageEvent) {
                    saveImageEvent((ImageEvent) events.get(i));
                } else if(events.get(i) instanceof TextEvent) {
                    saveTextEvent((TextEvent) events.get(i));
                } else if(events.get(i) instanceof VideoEvent) {
                    saveVideoEvent((VideoEvent) events.get(i));
                }else {
                    //System.out.println("Eventklasse nicht gefunden!!");
                }
            }
        }
    }
    
    /**
     * Updates a track and its metadata
     * 
     * @param track the track to update
     * @throws SQLException if there is an problem with the db
     */
    public void updateTrack(Track track) throws SQLException {
        if (track == null) {
            throw new NullPointerException();
        }
        
        trackDao.createOrUpdate(track);
        
        if(track.getMetadata() != null) {
            Metadata meta = track.getMetadata();
            meta.setParentTrack(track);
        
            metadataDao.createOrUpdate(meta);
        }
    }
    
    /**
     * Get all Events by the Track
     * 
     * @param track
     * @return 
     */
    public List<Event> getEventsByTrackId(Track track) throws SQLException {//need that exception for error handling
        List<Event> eventList = new ArrayList<Event>();
        
        if(imageEventDao != null) {
            //try {
                QueryBuilder<ImageEvent, Track> ibuilder = 
                        imageEventDao.queryBuilder();
                
                ibuilder.where().eq(Event.TRACK_TABLE_NAME, track);
                eventList.addAll(ibuilder.query());
                
                QueryBuilder<TextEvent, Track> tbuilder = 
                        textEventDao.queryBuilder();
                
                tbuilder.where().eq(Event.TRACK_TABLE_NAME, track);
                eventList.addAll(tbuilder.query());
                
                QueryBuilder<VideoEvent, Track> vbuilder =
                        videoEventDao.queryBuilder();
                vbuilder.where().eq(Event.TRACK_TABLE_NAME, track);
                eventList.addAll(vbuilder.query());
                
                for (Event e: eventList) {
                    e.setParentTrack(track);
                }
                
//            } catch(SQLException e) {
//                e.printStackTrace();
//            }
        }
        
        return eventList;
    }
    
    /**
     * Get the Track with the given id
     * 
     * @param id
     * @return
     * @throws SQLException 
     */
    public Track getTrack(Integer id) throws SQLException {
        
        if(trackDao != null) {
            Track track = trackDao.queryForId(id);
            
            return track;
        } else {
            throw new SQLException("Database connection lost");
        }
    }
    
    /**
     * Returns all Tracks.
     * 
     * @return A list containing all Tracks in the Database.
     * @throws SQLException 
     */
    public List<Track> getAllTracks() throws SQLException {
        List<Track> tracks = trackDao.queryForAll();
        
        for (Track t: tracks) {
            QueryBuilder<Metadata, Track> query = metadataDao.queryBuilder();
            query.where().eq(Metadata.TRACK_TABLE_NAME, t);
            t.setMetadata(query.query().get(0));
        }
        
        return tracks;
    }
    
//    /**
//     * Save the image file to the database.
//     * 
//     * @param image The image which should be saved.
//     * @throws SQLException 
//     */
//    public void saveImage(Image image) throws SQLException {
//        if(imageDao != null) {
//            imageDao.create(image);
//        }
//    }
    
//    /**
//     * Save the given image list to the database.
//     * 
//     * @param images The image list which should be saved.
//     * @throws SQLException 
//     */
//    public void saveImage(List<Image> images) throws SQLException {
//        
//        for(Image img: images) {
//            saveImage(img);
//        }
//    }
    
//    /**
//     * Get all images.
//     * 
//     * @return
//     * @throws SQLException 
//     */
//    public List<Image> getAllImages() throws SQLException {
//        if(imageDao != null) {
//            return imageDao.queryForAll();
//        }
//        return null;
//    }
    
    public void closeDatabaseConnection() throws SQLException {
        connectionSource.close();
    }
    
    /**
     * Remove an event from the database.
     * 
     * @param event The event which should be removed. 
     */
    public void removeEvent(Event event) throws SQLException {
        if(event instanceof ImageEvent) {
            imageEventDao.delete((ImageEvent)event);
        } else if(event instanceof VideoEvent) {
            videoEventDao.delete((VideoEvent) event);
        } else if(event instanceof TextEvent) {
            textEventDao.delete((TextEvent) event);
        } else {
            //System.out.println("Dafuq Event!");
        }
    }
    
    /**
     * This will remove the Track with all related objects 
     * from the database.
     * 
     * @param track The Track which should be removed
     * @throws Throws an SQLException if there is any connection 
     *      problem with the database.
     */
    public void deleteTrack(Track track) throws SQLException {
        List<Event> childEvents;
        
        childEvents = getEventsByTrackId(track);
        
        for(Event event: childEvents) {
            try {
                removeEvent(event);
            } catch(SQLException e) {
                System.out.println("Event: " + event.toString() +
                        " wurde nicht gelöscht");
            }
        }
        
        if(track.getMetadata() != null)
            metadataDao.delete(track.getMetadata());
        
        trackDao.delete(track);
    }
}
